//
//  NetManager.swift
//  YuanJuBian
//
//  Created by PerhapYs on 2021/12/16.
//

import Foundation

#if canImport(Alamofire) && canImport(HandyJSON)
import Alamofire
import HandyJSON

//网络请求错误码
enum ResponseError  :   Int {
    
    case    unkown      =    -1  //未知
    case    success     =   0 //成功
    case    needLogin   =   401 //需要登陆
    case    notAccess   =   403 // 权限不够
}
//发起请求参数发送方式
enum RequestParamType{
    
    case form     //表单
    case body     //数据体
}
//默认转模型结构
struct ResponseDefault : HandyJSON {}

/// 基本返回响应数据
struct ResponseData : HandyJSON{
    
    var code    :   Int?
    var msg     :   String?
    var data    :   Any?
    var dataList : [Any?]?
}

/// 通用返回响应数据
struct ResponseModel<T>{
    
    var errorCode       :   ResponseError = .unkown
    var errorMessage    :   String = "未知错误"
    var model           :   T?
    var models          :   [T?]?
    var resultData      :   Any?
}

/// 请求头结构
struct RequestHeaders : Encodable{
    
    var token           :   String?
    var apiVersion : String = AppInfo.appVersion
    var timestamp : TimeInterval = Date.timeIntervalSince1970()
    
    /// 生成请求头
    /// - Returns: 请求头
    static func makeHTTPHeaders(isNeedExtra:Bool = true) -> HTTPHeaders{
        
        var httpHeaders = HTTPHeaders()
        
        if isNeedExtra{
            var requestHeader = RequestHeaders()
            if ISLOGIN{
                requestHeader.token = UserManager.userInfo.token
            }
            PYLog(requestHeader.token,desc: "UserToken")
            PYLog(requestHeader.apiVersion, desc: "apiVersion")
            let headerDic = requestHeader.toDictionary()!.toStringValueDictionary()
            let paramHeaders = HTTPHeaders.init(headerDic)
            httpHeaders = paramHeaders
        }
        
        let header = HTTPHeader.init(name: "Content-Type", value: "application/json")
        httpHeaders.add(header)
        
        return httpHeaders
    }
}

class NetManager {
    
    /// 单例
    static let share : NetManager = {
        
        let manager = NetManager()
        manager.listenNetworkStatus()
        return manager
    }()
    
    /// 接口地址
    var RequestUrlHost : String {
        get{
            #if RELEASE
            return RequestUrlHost_RELEASE
            #else
            return RequestHostManager.Host
            #endif
        }
    }
    
    /// 参数编码方式  URLEncodedFormParameterEncoder / JSONParameterEncoder
    let YJBParameterEncoder : ParameterEncoder = URLEncodedFormParameterEncoder.default
    
    //下方属性为监听网络状态使用
    let semaphore = DispatchSemaphore.init(value: 1)
    
    //网络可用回调
    var networkSuccessedBlock : EmptyCompleteBlock?
    
    // 网络是否可用
    var isNetworkReachable  = NetworkReachabilityManager.default != nil ? NetworkReachabilityManager.default!.isReachable : false
    
    //文件下载
    var downloadManager : DownloadRequest?
    //已下载的数据
    var downloadedData : Data?
}

typealias ResponseBlock<T> = (_ responseModel:ResponseModel<T>) -> ()

//MARK: Request API
extension NetManager{
    ///可无参数，无模型数据返回
    class func request(url:String,
                       method:HTTPMethod = .post,
                       headers:HTTPHeaders = RequestHeaders.makeHTTPHeaders(),
                       paramType:RequestParamType = .body,
                       parametersDic:[String:String]? = nil,
                       resultBlock:ResponseBlock<ResponseDefault>?){
        self.request(url: url, method: method,headers: headers,paramType: paramType, parametersDic: parametersDic, modelType: ResponseDefault.self, resultBlock: resultBlock)
    }
    /// 可无参数
    class func request<T:HandyJSON>(url:String,
                                    method:HTTPMethod = .post,
                                    headers:HTTPHeaders = RequestHeaders.makeHTTPHeaders(),
                                    paramType:RequestParamType = .body,
                                    parametersDic:[String:String]? = nil,
                                    modelType:T.Type,
                                    resultBlock:ResponseBlock<T>?){
        let param = parametersDic != nil ? parametersDic : [:]
        self.request(url: url, method: method,headers: headers,paramType: paramType, parameters: param, modelType: modelType, resultBlock: resultBlock)
    }
    /// 无模型数据返回
    class func request<Parameters: Encodable>(url:String,
                                              method:HTTPMethod = .post,
                                              headers:HTTPHeaders = RequestHeaders.makeHTTPHeaders(),
                                              paramType:RequestParamType = .body,
                                              parameters:Parameters,
                                              resultBlock:ResponseBlock<ResponseDefault>?){
        self.request(url: url, method: method,headers: headers,paramType: paramType, parameters: parameters, modelType: ResponseDefault.self, resultBlock: resultBlock)
    }
    
    /// 数据模型返回
    class func request<T:HandyJSON,Parameters: Encodable>(url:String,
                                                          method:HTTPMethod = .post,
                                                          headers:HTTPHeaders = RequestHeaders.makeHTTPHeaders(),
                                                          paramType:RequestParamType = .body,
                                                          parameters:Parameters,
                                                          modelType:T.Type,
                                                          resultBlock:ResponseBlock<T>?)
    {
        self.networkReachable {
            
            NetManager.InitDataRequest(url: url,method: method,headers: headers,paramType: paramType,parameters: parameters)
                .responseData(completionHandler: { responseData in
                    
                    switch responseData.result{
                    case .failure(let error):
                        
                        PYLog("状态码:\(String(describing: responseData.response?.statusCode)) , 错误信息:\(error)")
                        var responseModel = ResponseModel<T>()
                        responseModel.errorCode = .unkown
                        responseModel.errorMessage = error.errorDescription ?? ""
                        resultBlock?(responseModel)

                        let errorCode = responseData.response?.statusCode
                        switch errorCode{
                        case 401:  //检测到http错误401.
                            UserManager.expired()
                            PYLoginManager.pushLoginViewController()
                        default:
                            break
                        }
                    case .success(let data):
                        let dataString = String.init(bytes: data, encoding: .utf8)
                        self.response(modelType, dataString,resultBlock)
                    }
                })
        }
    }
}
//MARK: Upload Image
extension NetManager{
    
    /// 上传单张图片
    /// - Parameters:
    ///   - url: 链接
    ///   - parameters: 参数
    ///   - imageData: 图片数据
    ///   - resultBlock: ～
    class func request<T:HandyJSON,Parameters: Encodable>(url:String,
                                                          parameters:Parameters,
                                                          imageData:Data,
                                                          modelType:T.Type,
                                                          resultBlock:ResponseBlock<T>?){
        
        self.request(url: url, parameters: parameters, imageDatas: [imageData], modelType: modelType, resultBlock: resultBlock)
    }
    
    /// 上传多张图片
    class func request<T:HandyJSON,Parameters: Encodable>(url:String,
                                                          parameters:Parameters,
                                                          imageDatas:[Data],
                                                          modelType:T.Type,
                                                          resultBlock:ResponseBlock<T>?)
    {
        guard imageDatas.count > 0 else {
            PYLog("请添加上传图片数据")
            return
        }
        let headers : HTTPHeaders = RequestHeaders.makeHTTPHeaders()
        let requestURL = url.jointHost()
        
        AF.upload(multipartFormData: { fromData in
            
            if let paraDic = parameters.toDictionary(){
                for key in paraDic.keys {
                    let value = "\(paraDic[key] ?? "")".data(using: .utf8)!
                    fromData.append(value, withName: key)
                }
            }
            if imageDatas.count > 1{ // 添加多张图片
                
                for (index,imageData) in imageDatas.enumerated() {
                    fromData.append(imageData, withName: "file[\(index)]", fileName: "file_\(index).jpg", mimeType: "image/png,image/jpeg,image/jpg")
                }
            }
            else{  //添加单张图片
                let imageData = imageDatas.first!
                fromData.append(imageData, withName: "file", fileName: "file.jpg", mimeType: "image/png,image/jpeg,image/jpg")
            }
            
        }, to: requestURL ,headers: headers)
        .responseString { string in
            
            self.response(modelType, string.value,resultBlock)
        }
    }
    
    /// 上传图片到OSS
    /// - Parameters:
    ///   - url: 上传地址
    ///   - accessId: ~
    ///   - expireTime: ~
    ///   - signature: ~
    ///   - policy: ~
    ///   - filePath: 图片地址
    ///   - imageName: 图片名称
    ///   - imageData: 图片内容
    ///   - successedBlock: ~
    ///   - failureBlock: ~
    class func uploadOss(url:String,
                          accessId:String,
                          expireTime:String,
                          signature:String,
                          policy:String,
                          filePath:String,
                          imageName:String,
                          imageData:Data,
                          successedBlock:@escaping ReturnValueBlock<String> ,
                          failureBlock:ReturnValueBlock<String?>?){
        
        let httpHeaders = HTTPHeaders()
        let key = filePath + imageName
        let param = ["OSSAccessKeyId":accessId,
                     "expireTime":expireTime,
                     "Signature":signature,
                     "policy":policy,
                     "name":imageName,
                     "key":key]
        AF.upload(multipartFormData: { formData in
            for key in param.keys {
                let value = "\(param[key] ?? "")".data(using: .utf8)!
                formData.append(value, withName: key)
            }
            formData.append(imageData, withName: "file", fileName: "file.jpg", mimeType: "image/png,image/jpeg,image/jpg")
        }, to: url,headers: httpHeaders)
        .response { result in
            switch result.result{
            case .failure(let error):
                
                PYLog("状态码:\(String(describing: result.response?.statusCode)) , 错误信息:\(error)")
                failureBlock != nil ? failureBlock!(error.errorDescription) : nil
                
            case .success(let data):
                print(data as Any)
                if let data = data{
                    let dataString = String.init(bytes: data, encoding: .utf8)
                    failureBlock != nil ? failureBlock!(dataString) : nil
                }
                else{
                    let imageUrl = url + "/" + key
                    successedBlock(imageUrl)
                }
            }
        }
    }
}
//MARK: Download
extension NetManager{
    
    typealias PYDownloadSuccess = (URL?) -> ()
    typealias PYDownloadFailure = (Error?) -> ()
    typealias PYDownloadProgress = (Progress) -> Void
    
    /// 下载文件
    /// - Parameters:
    ///   - remoteUrl: 文件远程地址
    ///   - localUrl: 文件下载到本地的地址
    ///   - progress: 下载进度条
    ///   - success: ~
    ///   - failure: ~
    func download(remoteUrl:String,localUrl:String, progress:PYDownloadProgress?,success:@escaping PYDownloadSuccess,failure:PYDownloadFailure?){
        
        guard let url = URL.init(string: remoteUrl) else {
            debugPrint("错误下载链接!")
            return
        }
        let destination : DownloadRequest.Destination = {temporaryURL,response in
            let destinationUrl = URL.init(fileURLWithPath: localUrl)
            return (destinationUrl,[.removePreviousFile,.createIntermediateDirectories])
        }
        let requestURL = URLRequest.init(url:url)
        
        var downloadRequest : DownloadRequest? = nil
        
        if let resumeData = self.downloadedData{
            downloadRequest = AF.download(resumingWith: resumeData)
        }
        else{
            downloadRequest = AF.download(requestURL, interceptor: nil, to: destination)
        }
        if let progress = progress {
            downloadRequest!.downloadProgress(closure: progress)
        }
        downloadRequest!.response(queue: DispatchQueue.main) {[weak self] response in
            if let weakSelf = self{
                weakSelf.downloadedData = response.resumeData
            }
            switch response.result{
            case .success(let url):
                success(url)
            case .failure(let error):
                failure?(error)
            }
        }
        self.downloadManager = downloadRequest
    }
    
    /// 暂停下载
    func suspendDownload(){
        
        self.downloadManager?.task?.suspend()
    }
    
    /// 取消下载
    func cancelDownload(){
        
        self.downloadManager?.task?.cancel()
        self.downloadManager = nil
    }
    
    /// 继续下载
    func resumeDownload(){
        
        self.downloadManager?.task?.resume()
    }
}
//MARK: Helper
extension NetManager{
    
    /// 生成发起请求对象
    /// - Parameters:
    ///   - url: 链接
    ///   - method: post/get
    ///   - paramType: 参数传输方式
    ///   - parameters: 参数
    /// - Returns: ~
    fileprivate class func InitDataRequest<Parameters: Encodable>(url:String,
                                                                  method:HTTPMethod = .post,
                                                                  headers:HTTPHeaders,
                                                                  paramType:RequestParamType = .body,
                                                                  parameters:Parameters? = nil) -> DataRequest{
        
        if paramType == .form{
            return self.InitFormDataRequest(url: url,method: method,headers: headers,parameters: parameters)
        }
        return self.InitBodyDataRequest(url: url,method: method,headers: headers,parameters: parameters)
    }
    
    /// form传输参数
    /// - Parameters:
    ///   - url: 链接
    ///   - method: post/get
    ///   - parameters: 参数
    /// - Returns: ~
    fileprivate class func InitFormDataRequest<Parameters: Encodable>(url:String,
                                                                      method:HTTPMethod = .post,
                                                                      headers:HTTPHeaders,
                                                                  parameters:Parameters? = nil) -> DataRequest{
        
        let encoder : ParameterEncoder = NetManager.share.YJBParameterEncoder
        let requestUrl = url.jointHost()
        PYLog(requestUrl, desc: "form请求地址")
        PYLog(String.init(describing: parameters) , desc: "form请求参数")
        let request : DataRequest = AF.request(requestUrl, method: method, parameters: parameters, encoder: encoder, headers: headers, interceptor: nil, requestModifier: nil)

        return request
    }

    /// Body传输参数
    /// - Parameters:
    ///   - url: 链接
    ///   - method: post/get
    ///   - parameters: 参数
    /// - Returns: ~
    fileprivate class func InitBodyDataRequest<Parameters: Encodable>(url:String,
                                                                      method:HTTPMethod = .post,
                                                                      headers:HTTPHeaders,
                                                                      parameters:Parameters? = nil) -> DataRequest{
        
        guard method == .post else {
            //GET请求不存在Body，使用form方式传输
            return self.InitFormDataRequest(url: url, method: method,headers: headers, parameters: parameters)
        }
        let requestUrl = url.jointHost()
        
        PYLog(requestUrl, desc: "body请求地址")
        var req = URLRequest(url: URL.init(string: requestUrl)!)
        req.httpMethod = method.rawValue
        req.headers = headers
        if let requestParam = parameters ,let jsonParam = requestParam.toJson(){
           
            PYLog(jsonParam, desc: "body请求参数")
            req.httpBody = jsonParam.data(using: .utf8)
        }
        req.setValue("application/json", forHTTPHeaderField: "Content-Type")
        let request = AF.request(req)
        return request
    }
    
    /// 解析服务器返回的数据
    /// - Parameters:
    ///   - modelType: 转model类型
    ///   - responseData: 响应数据
    ///   - resultBlock: 回调
    fileprivate class func response<T:HandyJSON>
    (
        _ modelType:T.Type,
        _ responseData:String?,
        _ resultBlock:ResponseBlock<T>?
    ){
        guard let resultBlock = resultBlock else {
            return
        }
        var responseModel = ResponseModel<T>()
        responseModel.resultData = responseData
        let baseModel = ResponseData.deserialize(from: responseData)
        
        guard let baseModel = baseModel else {
            return resultBlock(responseModel)
        }
        
        let errorCode = ResponseError(rawValue: baseModel.code ?? 0) ?? .unkown
        if errorCode != .success{
            PYLog(responseData, desc: "请求响应")
        }
        else{
            PYLog(baseModel.msg, desc: "请求响应")
        }
        responseModel.errorCode = errorCode
        
        if let _ = baseModel.msg{
            responseModel.errorMessage = baseModel.msg!
        }
        //当为默认模型，不需要转
        if modelType != ResponseDefault.self{
            //从data中获取原始数据
            if let data = baseModel.data{
                self.deserializeData(responseModel: &responseModel, originalData: data ,modelType:modelType)
            }
            //从dataList中获取原始数据
            else if let data = baseModel.dataList {
                self.deserializeData(responseModel: &responseModel, originalData: data ,modelType:modelType)
            }
        }
        return resultBlock(responseModel)
    }
    
    /// 解析为Model数据
    /// - Parameters:
    ///   - responseModel: 回调存储解析结果
    ///   - originalData: 需要解析的原始Data
    ///   - modelType: 解析Model类型
    class func deserializeData<T:HandyJSON>(responseModel:inout ResponseModel<T> , originalData:Any , modelType:T.Type){
        
        //单独处理返回为字符串，或者为字符串数组
        if T.self is String.Type{
            if let data = originalData as? [T?]{
                responseModel.models = data
            }
            else if let data = originalData as? T{
                responseModel.model = data
            }
            else{
                responseModel.resultData = originalData
            }
        }
        //解析字典
        else if let data = originalData as? [String : Any]{
            
            responseModel.model = T.deserialize(from: data)
        }
        // 解析字典数组
        else if let dataArray = originalData as? [[String:Any]]{
            
            responseModel.models = [T].deserialize(from: dataArray)
        }
        //其他解析
        else{
            responseModel.resultData = self.deserializeArray(originalData: originalData,modelType: modelType)
        }
    }
    
    /// 解析非字典，字典数组，的其他类型
    /// - Parameters:
    ///   - originalData: 原始Data
    ///   - modelType: 解析Model结构
    /// - Returns: 解析结果
    class func deserializeArray<T:HandyJSON>(originalData:Any , modelType:T.Type) -> Any{
        
        //如果为数组
        if let originalDataArr = originalData as? [Any]{
            var array = [Any]()
            for childData in originalDataArr {
                //如果数组下是字典
                if let dataDic = childData as? [String : Any],
                   let model = T.deserialize(from: dataDic){
                    array.append(model)
                }
                //如果是数组字典。
                else if let dataArr = childData as? [[String : Any]],
                        let modelArr = [T].deserialize(from: dataArr){
                    array.append(modelArr)
                }
                //其他解析
                else{
                    let deserialData = self.deserializeArray(originalData: childData, modelType: modelType)
                    array.append(deserialData)
                }
            }
            return array
        }
        // 即不是字典，又不是数组，返回原始数据(视为基本数据类型).
        else{
            return originalData
        }
    }
}
//MARK: NetworkStatus
extension NetManager{
    
    //检查当前网络是否可用
    class func networkReachable(_ networkReachableBlock:@escaping EmptyCompleteBlock){
        
        guard !share.isNetworkReachable else {//网络当前可用，直接触发请求
            return networkReachableBlock()
        }
        //开启异步，等待网络变更.
        DispatchQueue.global().async {
            share.semaphore.wait()  //如果有其他请求进入，进入无限期等待
            if share.isNetworkReachable{
                share.semaphore.signal()  //等待完成后，如果网络状态变更为可用，则返回,重置信号量
                return networkReachableBlock()
            }
            share.networkSuccessedBlock = {  // 网络变更为可用才有的回调
                share.semaphore.signal() // 接收到网络变更为可用，重置信号量
            }
            share.semaphore.wait() //无限期等待网络变更为可用后，回调继续执行请求
            networkReachableBlock()
        }
    }
    
    /// 监听网络状态
    func listenNetworkStatus(){
        
        guard let _ = NetworkReachabilityManager.default else {
            return
        }
        NetworkReachabilityManager.default!.startListening(onQueue: DispatchQueue.main, onUpdatePerforming: { status in
            switch status {
            case .unknown:
                fallthrough
            case .notReachable:
                self.isNetworkReachable = false
            case .reachable(.ethernetOrWiFi):
                fallthrough
            case .reachable(.cellular):
                self.isNetworkReachable = true
                if let _ = self.networkSuccessedBlock{
                    self.networkSuccessedBlock!()
                }
            }
        })
    }
}
fileprivate extension String{
    
    func jointHost() -> String{
        
        let host = NetManager.share.RequestUrlHost
        guard !self.isEmpty else {
            return host
        }
        guard !self.contains("http") else {
            return self
        }
        return host + self
    }
}
#endif
